// RedJey Graphics User Interface Library for J2ME Platform.
// Copyright (C) 2008 Yevgen Krapiva
//
// This library is free software; you can redistribute
// it and/or modify it under the terms of the
// GNU Lesser General Public License as published
// by the Free Software Foundation; either version 3.0
// of the License, or any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package com.incrediblesoft.redjey;

/**
 * @author Yevgen Krapiva<br>
 *         Homepage: http://code.google.com/p/redjey/<br>
 *         Feedback: ykrapiva@gmail.com<br>
 *         Copyright (C) 2008 Yevgen Krapiva<br>
 */

import com.incrediblesoft.redjey.layouts.Layout;
import com.incrediblesoft.redjey.layouts.RowLayout;
import com.incrediblesoft.redjey.event.MouseEvent;
import com.incrediblesoft.redjey.event.AbstractEvent;

import javax.microedition.lcdui.Graphics;
import java.util.Vector;
import java.util.Hashtable;


public class Container extends Component
{
    /**
     * Components storage
     */
    protected final Vector components = new Vector();
    /**
     * Layout method that is applied to this container.
     */
    protected Layout layout;
    /**
     * Component that recieved MOUSE_ENTER message before
     */
    protected Component prevEnteredComponent;
    /**
     * Current focused component
     */
    protected Component focusOwner;
    /**
     * Focus context
     */
    protected int focusContext;
    /**
     * Cycle focus flag
     */
    protected boolean isFocusCycled;

    /**
     * Constructs default container
     */
    public Container()
    {
        this(UIManager.DEFAULT_SET);
    }

    /**
     * Constructs container with given style class name
     *
     * @param styleClassName style class name
     */
    public Container(String styleClassName)
    {
        super(styleClassName);

        setLayout(new RowLayout());
    }

    public String getName()
    {
        return "Container";
    }

    protected Dimension calcPreferredSize(int paddingHorizontal, int paddingVertical, CustomFont font, Hashtable attributes)
    {
        if (layout == null)
        {
            int maxWidth = 0, maxHeight = 0;

            for (int i = 0; i < getComponentCount(); i++)
            {
                Component c = (Component) components.elementAt(i);
                Dimension preferredSize = c.getPreferredSize();
                maxWidth = Math.max(maxWidth, c.x + preferredSize.width);
                maxHeight = Math.max(maxHeight, c.y + preferredSize.height);
            }

            return new Dimension(maxWidth, maxHeight);
        }
        else
        {
            return layout.preferredLayoutSize(this);
        }
    }

    /**
     * Returns the count of components
     *
     * @return count
     */
    public int getComponentCount()
    {
        return components.size();
    }

    /**
     * Returns component at specified index
     *
     * @param index index of component
     * @return component
     */
    public Component getComponent(int index)
    {
        if (index >= 0 && index < components.size())
            return (Component) components.elementAt(index);
        else
            return null;
    }

    /**
     * Returns component located under the x,y position in this container coordinate system.
     * Performs in-depth search.
     * 
     * @param x the x coordinate
     * @param y the y coordinate
     * @param deepSearch sets search mode. False means that only top-level
     * component will be returned, true - means perform in-septh search
     * @return null if the component does not contain the position.
     * If there is no child component at the requested point null is returned.
     */
    public Component getComponentAt(int x, int y, boolean deepSearch)
    {
        for (int i=0; i<components.size(); i++)
        {
            Component c = (Component) components.elementAt(i);

            if (c.getBounds().inside(x, y))
            {
                if (deepSearch && (c instanceof Container))
                {
                    Component c2 = ((Container) c).getComponentAt(x - c.x, y - c.y, deepSearch);
                    if (c2 == null) c2 = c;
                    return c2;
                }
                else
                    return c;
            }
        }

        return null;
    }

    /**
     * Adds specified component to this container

     * @param c component
     * @return component
     */
    public Component add(Component c)
    {
        components.addElement(c);
        c.parent = this;
        invalidate();
        return c;
    }

    /**
     * Inserts component at specified index
     *
     * @param c component
     * @param index index the component is inserted to
     * @return component itself
     */
    public Component insertAt(Component c, int index)
    {
        components.insertElementAt(c, index);
        c.parent = this;
        invalidate();
        return c;
    }

    /**
     * Removes specified component from this container
     *
     * @param c component
     */
    public void remove(Component c)
    {
        components.removeElement(c);
        if (prevEnteredComponent == c) prevEnteredComponent = null;
        invalidate();
    }

    /**
     * Removes component at specified index
     *
     * @param index index
     */
    public void remove(int index)
    {
        Component c = (Component) components.elementAt(index);
        if (c != null) remove(c);
    }

    /**
     * Removes all the components from this container
     */
    public void removeAll()
    {
        components.removeAllElements();
        prevEnteredComponent = null;
        //prevPressedComponent = null;
        invalidate();
    }

    /**
     * Returns the index of specified component within this container
     * @param c component
     * @return index of component, -1 if non was found
     */
    public int indexOf(Component c)
    {
        return components.indexOf(c);
    }

    /**
     * Sets layout to this container
     *
     * @param layout layout
     */
    public void setLayout(Layout layout)
    {
        this.layout = layout;
        invalidate();
    }

    /**
     * @return layout
     */
    public Layout getLayout()
    {
        return layout;
    }

    /**
     * Sets the size of container to preferred one.
     */
    public void pack()
    {
        setSize(getPreferredSize());
    }

    public void dispatchEvent(AbstractEvent event)
    {
        Component source = (Component) event.getSource();
        if (source == this)
            super.dispatchEvent(event);
        else if (source.parent == this)
            source.dispatchEvent(event);
    }
    
    protected void processMouseEvent(MouseEvent event)
    {
        // Find child component under mouse cursor
        Component target = getComponentAt(event.getX(), event.getY(), false);

        if (target != null)
        {
            event = UIManager.retargetMouseEvent(event, target);
            dispatchEvent(event);
        }

        if (!event.isConsumed())
            super.processMouseEvent(event);
    }

    protected void processMouseScrollEvent(MouseEvent event)
    {
        super.processMouseScrollEvent(event);

        Component target = getComponentAt(event.getX(), event.getY(), false);
        if (target != null)
        {
            MouseEvent newEvent = UIManager.retargetMouseEvent(event, target);
            dispatchEvent(newEvent);

            if (newEvent.isConsumed())
                event.consume();
        }
    }


    public boolean keyPressed(int keycode)
    {
        return false;
    }

    public boolean keyRepeated(int keycode)
    {
        return false;
    }

    public boolean keyReleased(int keycode)
    {
        return false;
    }

    /**
     * Returns true if this container contains focusable child components, false otherwise
     * @return true / false
     */
    public boolean isFocusCycleRoot()
    {
        boolean focusCycleRoot = false;

        for (int i=0; i<components.size() ;i++)
        {
            Component component = (Component) components.elementAt(i);
            if (component instanceof Container) focusCycleRoot = ((Container) component).isFocusCycleRoot();
            else focusCycleRoot = component.isFocusable();
            if (focusCycleRoot) break;
        }

        return focusCycleRoot;
    }

    public boolean isFocusCycled()
    {
        return isFocusCycled;
    }

    public void setFocusCycled(boolean focusCycled)
    {
        isFocusCycled = focusCycled;
    }

    public boolean transferFocusDownCycle()
    {
        return transferFocus();
    }

    /**
     * Returns the next focusable component relative to the specified one.
     * If focus is cycled and the search process reaches the end of container,
     * then the search is started from top.
     * @param component The component to look after.
     * @return Next focusable component, null if none was found.
     */
    public Component getComponentAfter(Component component)
    {
        int index = component != null ? components.indexOf(component) : -1;
        Component nextComponent;

        do
        {
            index++;

            if (isFocusCycled && index >= components.size()) index = 0;

            nextComponent = getComponent(index);                        

            if (nextComponent != null && nextComponent instanceof Container && ((Container) nextComponent).focusContext == this.focusContext && ((Container) nextComponent).isFocusCycleRoot())
            {
                nextComponent = ((Container) nextComponent).getComponentAfter(null);
            }
        }
        while(nextComponent != null && (!nextComponent.isFocusable() || !nextComponent.isVisible() || !nextComponent.isFocusable()));

        return nextComponent;
    }

    /**
     * Returns the previous focusable component relative to the specified one.
     * If focus is cycled and the search process reaches the top of container,
     * then the search is started from the bottom.
     * @param component The component to look before.
     * @return Next focusable component, null if none was found.
     */
    public Component getComponentBefore(Component component)
    {
        int index = component != null ? components.indexOf(component) : components.size();
        Component nextComponent;

        do
        {
            index--;

            if (isFocusCycled && index < 0) index = components.size() - 1;

            nextComponent = getComponent(index);

            if (nextComponent != null && nextComponent instanceof Container && ((Container) nextComponent).focusContext == this.focusContext && ((Container) nextComponent).isFocusCycleRoot())
            {
                nextComponent = ((Container) nextComponent).getComponentBefore(null);
            }
        }
        while(nextComponent != null && (!nextComponent.isFocusable() || !nextComponent.isVisible()));

        return nextComponent;
    }


    protected void applyContentAlignment(Graphics g)
    {
        // do nothing
        // container's (heavylight component) content must be controlled by layout manager
    }

    protected void validateComponent()
    {
        if (layout != null)
            layout.layoutContainer(this);
        else
            for (int i=0; i<components.size(); i++)
                ((Component) components.elementAt(i)).validate();
    }

    public void tick(int dt)
    {
        super.tick(dt);

        for (int i = 0; i < components.size(); i++)
            ((Component) components.elementAt(i)).tick(dt);
    }

    protected void paintComponent(Graphics g)
    {
        //int num = 0;
        //Current clip rect
        int clipX = g.getClipX();
        int clipY = g.getClipY();
        int clipW = g.getClipWidth();
        int clipH = g.getClipHeight();

        for (int i = 0; i < components.size(); i++)
        {
            Component c = (Component) components.elementAt(i);

            if (c.getBounds().intersects(clipX, clipY, clipW, clipH))
            {
                printComponent(g, c);
                //num++;
            }
        }

       //System.out.println("Container "+toString()+" has drawn "+num+" of "+getComponentCount()+" components");
    }

    /**
     * This function translated coordinate system to component's position,
     * clips the region and renders the component.
     * @param g Graphics object
     * @param c Component to render
     */
    protected void printComponent(Graphics g, Component c)
    {
        saveClip(g);
        saveTranslate(g);
        g.translate(c.x,  c.y);
        c.paint(g);
        restoreTranslate(g);
        restoreClip(g);
    }


}